package nl.unimaas.micc.eventSnookerTest.simulation;

import java.awt.Color;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import nl.unimaas.micc.eventSnookerTest.adt.def.Comparator;
import nl.unimaas.micc.eventSnookerTest.adt.def.List;
import nl.unimaas.micc.eventSnookerTest.adt.def.PriorityQueue;
import nl.unimaas.micc.eventSnookerTest.adt.impl.AVLTree;
import nl.unimaas.micc.eventSnookerTest.adt.impl.DoubleLinkedList;
import nl.unimaas.micc.eventSnookerTest.domain.BilliardBall;
import nl.unimaas.micc.eventSnookerTest.domain.Ball.BallState;
import nl.unimaas.micc.eventSnookerTest.exec.Tester;
import nl.unimaas.micc.eventSnookerTest.math.Vector3D;
import nl.unimaas.micc.eventSnookerTest.simulation.event.CushionCollision;
import nl.unimaas.micc.eventSnookerTest.simulation.event.Event;
import nl.unimaas.micc.eventSnookerTest.simulation.event.Interaction;
import nl.unimaas.micc.eventSnookerTest.simulation.event.StateChange;
import nl.unimaas.micc.eventSnookerTest.simulation.physics.Physics;

public class Simulation
{
	private static double currTime;

	private static AVLTree<Double, Event> events;
	private static Map<BilliardBall, List<Event>> ballEvents;
	private static Map<BilliardBall, BilliardBall> initials;

	public static void start(BilliardBall[] balls)
	{

		Comparator<Double> timeComp = new Comparator<Double>()
		{
			private final static double EPSILON = 0.000001;

			public boolean isComparable(Double a)
			{
				return true;
			}

			public boolean isEqual(Double a, Double b)
			{
				return Math.abs(a - b) < EPSILON;
			}

			public boolean isGreater(Double a, Double b)
			{
				return a > b && !isEqual(a, b);
			}

			public boolean isGreaterOrEqual(Double a, Double b)
			{
				return a > b || isEqual(a, b);
			}

			public boolean isLess(Double a, Double b)
			{
				return a < b && !isEqual(a, b);
			}

			public boolean isLessOrEqual(Double a, Double b)
			{
				return a < b || isEqual(a, b);
			}

		};
		events = new AVLTree<Double, Event>(timeComp);
		ballEvents = new HashMap<BilliardBall, List<Event>>();
		initials = new HashMap<BilliardBall, BilliardBall>();
		for(BilliardBall ball : balls){
			ballEvents.put(ball, new DoubleLinkedList<Event>());	
			initials.put(ball, (BilliardBall) ball.clone());
			queueEvents(initials.get(ball));
		}
		currTime = 0;


		// for (double i = 0; i < 2; i+=0.05) {
		// for (double j = 0; j < 2; j+= 0.05) {
		// Ball newBall1 = (Ball) b0.clone();
		// newBall1.setPosition(new Vector3D(i, j, 0));
		// Event e = new Interaction(newBall1,b1);
		// }
		// }

	}

	public static void queueEvents(BilliardBall ball)
	{
		List<Event> l = ballEvents.get(ball);
		
		
		
		if (ball.getState() != BallState.RESTING)
		{
			StateChange sc = new StateChange(ball);

			l.insertLast(sc);
			sc.addListOccurrence(l.last());
			sc.setPQOccurrence(events.insertItem(sc.getTime(), sc));

			// Mark event in Tester
			Tester.markPoint(ball.getState().getMotion().getPosition(ball,
					sc.getTime()).x, ball.getState().getMotion().getPosition(ball,
							sc.getTime()).y, sc.toString(), Color.GREEN);
		}

		CushionCollision cc = CushionCollision.find(ball);
		if(cc != null){
			l.insertLast(cc);
			cc.addListOccurrence(l.last());
			cc.setPQOccurrence(events.insertItem(cc.getTime(), cc));
			Tester.markPoint(ball.getState().getMotion().getPosition(ball,
					cc.getTime()).x, ball.getState().getMotion().getPosition(ball,
							cc.getTime()).y, cc.toString(), Color.BLUE);
			
		}


		for (Interaction in : Interaction.find(ball, initials.values()))
		{
			for (BilliardBall b : in.getBalls())
			{
				l = ballEvents.get(b);
				l.insertLast(in);
				in.addListOccurrence(l.last());
			}
			in.setPQOccurrence(events.insertItem(in.getTime(), in));
			Tester.markPoint(ball.getState().getMotion().getPosition(ball,
					in.getTime()).x, ball.getState().getMotion().getPosition(
					ball, in.getTime()).y, in.toString(), Color.RED);

		}

		if (Tester.detailMode)
		{
/*
			// Mark path of collision detection
			for (double i = 0; i < 2; i += 0.02)
			{
				for (double j = 0; j < 2; j += 0.02)
				{
					BilliardBall imaginaryBall = (BilliardBall) ball.clone();
					imaginaryBall.setRadius(0.005);
					imaginaryBall.setState(BallState.RESTING);
					imaginaryBall.setPosition(new Vector3D(i, j, 0));
					imaginaryBall.setVelocity(Vector3D.nullVector());
					imaginaryBall.setAngularVelocity(Vector3D.nullVector());
					if (Physics.collisionTime(imaginaryBall, ball) >= 0)
					{
						if (ball.getState() == BallState.SLIDING)
						{
							Tester.drawBall(imaginaryBall, Color.ORANGE);
						}
						else
						{
							Tester.drawBall(imaginaryBall, Color.YELLOW);
						}
					}
				}
			}
			*/
			// Mark path the ball will take
			BilliardBall imaginaryBall = new BilliardBall(3);
			imaginaryBall.setRadius(0.01);
			imaginaryBall.setState(BallState.RESTING);
			Color c1 = new Color(100, 100, 100, 200);
			Color c2 = new Color(200, 50, 100, 200);
			Color c3 = new Color(100, 50, 200, 200);

			for (double i = 0; i < 10; i += 0.02)
			{
				imaginaryBall.setPosition(ball.getState().getMotion()
						.getPosition(ball, i));
				imaginaryBall.setVelocity(ball.getState().getMotion()
						.getVelocity(ball, i));
				imaginaryBall.setAngularVelocity(ball.getState().getMotion()
						.getAngularVelocity(ball, i));

				Tester.drawBall(imaginaryBall, c1);
				Tester.drawV(imaginaryBall, c1, 0.5);
				Tester.drawAV(imaginaryBall, c2, 0.002);
				Tester.drawU(imaginaryBall, c3, 0.1);

			}

			// Pausing
			Tester.pause();
		}

	}
	
	public static void getBall(BilliardBall b)
	{
		BilliardBall initial = initials.get(b);
		if (initial.getState() != BallState.RESTING)
		{

			b.setPosition(initial.getState().getMotion().getPosition(initial,
					currTime));
			b.setVelocity(initial.getState().getMotion().getVelocity(initial,
					currTime));
			b.setAngularVelocity(initial.getState().getMotion()
					.getAngularVelocity(initial, currTime));
		}
		else
		{
			b.setPosition(initial.getPosition());
			b.setVelocity(initial.getVelocity());
		}
		b.setState(initial.getState());
		b.setTime(currTime);
	}

	public static void advanceTime(double t)
	{
		if (t <= 0)
		{
			throw new RuntimeException();
		}
		currTime += t;

			if (events.size() <= 1)
			{
				return;
			}

			if (currTime > events.minKey())
			{
				// handle the next event
				Event e = events.minElement();
				System.out.println("EVENT: " + e.toString());
				e.handle();
				for (BilliardBall ball : e.getBalls())
				{
					// go through all balls that are involved
					for (Event sEvent : ballEvents.get(ball))
					{
						// go through all events scheduled for this ball
						// Unschedule the event!
						sEvent.removeOccurrences();
						System.out.println("removed "+sEvent);
					}
					// clear the ball-event list
					ballEvents.put(ball, new DoubleLinkedList<Event>());
					queueEvents(ball);
				}
			}
	}

}
